//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe 
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.
#include <stdio.h>
#include <stdlib.h>

#define MAX_PARTICLES   1000
/* rainbow mode (1 = yes, 0 = no) */
int rainbow = 1;

/* space pressed (1 = yes, 0 = no) */
int sp;

/* enter pressed (1 = yes, 0 = no) */
int rp;

float slowdown = 2.0f;
float xspeed;
float yspeed;
float zoom = -40.0f;

GLuint  loop;
GLuint  col;
GLuint  delay;

typedef struct
{
	int     active;     //1 = true, 0 = false
	float   life;
	float   fade;

	float   r;
	float   g;
	float   b;

	float   x;
	float   y;
	float   z;

	float   xi;
	float   yi;
	float   zi;

	float   xg;
	float   yg;
	float   zg;
}
particles;

particles particle[MAX_PARTICLES];


static GLfloat colors[12][3] = 
{
	{1.0, 0.5, 0.5}, {1.0, 0.75, 0.5},{1.0, 1.0, 0.5}, {0.75, 1.0, 0.5},
	{0.5, 1.0, 0.5}, {0.5, 1.0, 0.75},{0.5, 1.0, 1.0}, {0.5, 0.75, 1.0},
	{0.5, 0.5, 1.0}, {0.75, 0.5, 1.0},{1.0, 0.5, 1.0}, {1.0, 0.5, 0.75},
};


/* ascii code for the escape key */
#define ESCAPE 27

/* The number of our GLUT window */
int window; 


GLfloat     xrot;
GLfloat     yrot;
GLfloat     zrot;
GLuint      texture[1];

struct Image {
	unsigned long sizeX;
	unsigned long sizeY;
	char* data;
};
typedef struct Image Image;

int ImageLoad(char* filename, Image* image)
{
	FILE* file;
	unsigned long size;
	unsigned long i;
	unsigned short int planes;
	unsigned short int bpp;
	char temp;

	if ((file = fopen(filename, "r")) == NULL)
	{
		printf("File Not Found: %s\n", filename);
		return 0;
	}

	fseek(file, 18, SEEK_CUR);

	if((i = fread(&image->sizeX, 4, 1, file)) != 1)
	{
		printf("Error reading width from %s.\n", filename);
		return 0;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);

	if((i = fread(&image->sizeY, 4, 1, file)) != 1)
	{
		printf("Error reading height from %s.\n", filename);
		return 0;
	}
	printf("Height of %s: %lu\n", filename, image->sizeY);

	size = image->sizeX * image->sizeY * 3;

	if ((i = fread(&planes, 2, 1, file)) != 1)
	{
		printf("Planes from %s is not 1: %u\n", filename, planes);
		return 0;
	}

	if ((i = fread(&bpp, 2, 1, file)) != 1)
	{
		printf("Error reading bpp from %s.\n", filename);
		return 0;
	}

	if(bpp != 24)
	{
		printf("Bpp from %s is not 24: %u\n", filename, bpp);
		return 0;
	}

	fseek(file, 24, SEEK_CUR);

	image->data = (char*)malloc(size);
	if(image->data == NULL)
	{
		printf("Error allocation memory for color-corrected image data");
		return 0;
	}

	if((i = fread(image->data, size, 1, file)) != 1)
	{
		printf("Error readint image data from %s.\n", filename);
		return 0;
	}

	for (i = 0; i < size; i += 3)
	{
		temp = image->data[i];
		image->data[i] = image->data[i+2];
		image->data[i+2] = temp;
	}

	return 1;
}

void LoadGLTextures()
{
	Image* image1;

	image1 = (Image*) malloc(sizeof(Image));
	if(image1 == NULL)
	{
		printf("Error allocating space for image.");
		exit(0);
	}

	if (!ImageLoad("Data/lesson19/Particle.bmp", image1))
	{
		exit(1);
	}

	glGenTextures(1, &texture[0]);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);


	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
	LoadGLTextures();
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);		// This Will Clear The Background Color To Black
	glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
	glDisable(GL_DEPTH_TEST);
	//glDepthFunc(GL_LESS);				// The Type Of Depth Test To Do
	//glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
	glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture[0]);

	for (loop = 0; loop < MAX_PARTICLES; ++loop)
	{
		particle[loop].active = 1;
		particle[loop].life = 1.0f;


		particle[loop].fade = (float)(rand() % 100) / 1000.0f + 0.003f;

		particle[loop].r = colors[(int)(loop * (12.0 / MAX_PARTICLES))][0];
		particle[loop].g = colors[(int)(loop * (12.0 / MAX_PARTICLES))][1];
		particle[loop].b = colors[(int)(loop * (12.0 / MAX_PARTICLES))][2];

		particle[loop].xi = (float)((rand() % 50) - 26.0f)*10.0f;
		particle[loop].yi = (float)((rand() % 50) - 25.0f)*10.0f;
		particle[loop].zi = (float)((rand() % 50) - 25.0f)*10.0f;

		particle[loop].xg = 0.0f;
		particle[loop].yg = -0.8f;
		particle[loop].zg = 0.0f;
	}

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();				// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
	if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
		Height=1;

	glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,200.0f);
	glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
void DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
	glLoadIdentity();				// Reset The View

	for (loop = 0; loop < MAX_PARTICLES; ++loop)
	{
		if (particle[loop].active)
		{
			float x = particle[loop].x;
			float y = particle[loop].y;
			float z = particle[loop].z + zoom;

			glColor4f(particle[loop].r, particle[loop].g, particle[loop].b, particle[loop].life);

			glBegin(GL_TRIANGLE_STRIP);
			glTexCoord2d(1, 1); glVertex3f(x+0.5f, y+0.5f, z);
			glTexCoord2d(0, 1); glVertex3f(x-0.5f, y+0.5f, z);
			glTexCoord2d(1, 0); glVertex3f(x+0.5f, y-0.5f, z);
			glTexCoord2d(0, 0); glVertex3f(x-0.5f, y-0.5f, z);
			glEnd();

			particle[loop].x += particle[loop].xi / (slowdown * 1000);
			particle[loop].y += particle[loop].yi / (slowdown * 1000);
			particle[loop].z += particle[loop].zi / (slowdown * 1000);

			particle[loop].xi += particle[loop].xg;
			particle[loop].yi += particle[loop].yg;
			particle[loop].zi += particle[loop].zg;

			particle[loop].life -= particle[loop].fade;


			if (particle[loop].life < 0.0f)
			{
				particle[loop].life = 1.0f;
				particle[loop].fade = (float)((rand() % 100)/1000.0f + 0.003f);

				particle[loop].x = 0.0f;
				particle[loop].y = 0.0f;
				particle[loop].z = 0.0f;

				particle[loop].xi = xspeed + (float)((rand() % 60) - 32.0f);
				particle[loop].yi = yspeed + (float)((rand() % 60) - 30.0f);
				particle[loop].zi = (float)((rand() % 60) - 32.0f);

				col = rand() % 11;
				particle[loop].r = colors[col][0];
				particle[loop].g = colors[col][1];
				particle[loop].b = colors[col][2];
			}
		}
	}

	// since this is double buffered, swap the buffers to display what just got drawn.
	glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
	/* avoid thrashing this procedure */
	usleep(100);

	switch(key)
	{
	/* If escape is pressed, kill everything. */
		case ESCAPE: 
		/* shut down our window */
		glutDestroyWindow(window); 

		/* exit the program...normal termination. */
		exit(0);                   
		case GLUT_KEY_PAGE_UP:
		zoom += 0.1f;
		break;
		case GLUT_KEY_PAGE_DOWN:
		zoom -= 0.1f;
		break;
		case '+':
		slowdown -= 0.01f;
		break;
		case '-':
		slowdown += 0.01f;
		break;
		case GLUT_ENTERED:
		rainbow = rainbow == 0 ? 1 : 0;
		break;
		case GLUT_KEY_LEFT:
		xspeed -= 1.0f;
		break;
		case GLUT_KEY_RIGHT:
		xspeed += 1.0f;
		break;
		case GLUT_KEY_UP:
		yspeed += 1.0f;
		break;
		case GLUT_KEY_DOWN:
		yspeed -= 1.0f;
		break;
	}
	if (key == ' ' || (rainbow && delay > 25))
	{
		if (key == ' ')
			rainbow = 0;
		delay = 0;
		col++;
		if (col > 11)
			col = 0;
	}
	glutPostRedisplay();
}

int main(int argc, char **argv) 
{  
	/* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
	   X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
	glutInit(&argc, argv);  

	/* Select type of Display mode:   
	   Double buffer 
	   RGBA color
	   Alpha components supported 
	   Depth buffer */  
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

	/* get a 640 x 480 window */
	glutInitWindowSize(640, 480);  

	/* the window starts at the upper left corner of the screen */
	glutInitWindowPosition(0, 0);  

	/* Open a window */  
	window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");  

	/* Register the function to do all our OpenGL drawing. */
	glutDisplayFunc(&DrawGLScene);  

	/* Go fullscreen.  This is as soon as possible. */
	glutFullScreen();

	/* Even if there are no events, redraw our gl scene. */
	glutIdleFunc(&DrawGLScene);

	/* Register the function called when our window is resized. */
	glutReshapeFunc(&ReSizeGLScene);

	/* Register the function called when the keyboard is pressed. */
	glutKeyboardFunc(&keyPressed);

	/* Initialize our window. */
	InitGL(640, 480);

	/* Start Event Processing Engine */  
	glutMainLoop();  

	return 1;
}

